package com.gitee.wsl.jvm.format

import org.w3c.dom.Document
import org.xml.sax.InputSource
import org.xml.sax.SAXParseException
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.StringWriter
import java.math.BigDecimal
import java.math.RoundingMode
import java.nio.charset.Charset
import java.text.DecimalFormat
import java.text.NumberFormat
import java.text.StringCharacterIterator
import java.util.Currency
import java.util.Locale
import javax.xml.XMLConstants
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerException
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import java.lang.StringBuilder

object FormatExt {

    const val S2 = "%s%s"
    const val S2_HYPHEN = "%s-%s"
    const val S2_UNDERSCORE = "%s_%s"
    const val S2_COMMA = "%s,%s"
    const val S2_COMMA_SPACE = "%s, %s"
    const val S2_COMMA2 = "%s、%s"
    const val S2_SPACE = "%s %s"
    const val S2_SPACE_SE = " %s %s "

    const val S3 = "%s%s%s"
    const val S3_HYPHEN = "%s-%s-%s"
    const val S3_UNDERSCORE = "%s_%s_%s"
    const val S3_COMMA = "%s,%s,%s"
    const val S3_COMMA_SPACE = "%s, %s, %s"
    const val S3_COMMA2 = "%s、%s、%s"
    const val S3_SPACE = "%s %s %s"
    const val S3_SPACE_SE = " %s %s %s "

    const val S4 = "%s%s%s%s"
    const val S4_HYPHEN = "%s-%s-%s-%s"
    const val S4_UNDERSCORE = "%s_%s_%s_%s"
    const val S4_COMMA = "%s,%s,%s,%s"
    const val S4_COMMA_SPACE = "%s, %s, %s, %s"
    const val S4_COMMA2 = "%s、%s、%s、%s"
    const val S4_SPACE = "%s %s %s %s"
    const val S4_SPACE_SE = " %s %s %s %s "

    const val BRACE = "{ %s }"
    const val BRACE_SPACE = " { %s } "

    const val BRACKET = "[ %s ]"
    const val BRACKET_SPACE = " [ %s ] "

    const val PARENTHESES = "( %s )"
    const val PARENTHESES_SPACE = " ( %s ) "


    fun formatMoney(value: Double, currencyCode: String): String {
        val format = NumberFormat.getCurrencyInstance(Locale.getDefault())
        if (format is DecimalFormat) {
            val dfs = DecimalFormat().decimalFormatSymbols
            dfs.currency = Currency.getInstance(currencyCode)
            format.decimalFormatSymbols = dfs
            format.minimumFractionDigits = 2
            format.maximumFractionDigits = 2
            format.isGroupingUsed = true
            format.roundingMode = RoundingMode.HALF_UP
        }

        return format.format(value)
    }

    fun formatDouble(value: Double, digits: Int = 2, isGroupingUsed: Boolean = true): String {
        val format = DecimalFormat()
        format.minimumFractionDigits = digits
        format.maximumFractionDigits = digits
        format.isGroupingUsed = isGroupingUsed
        format.roundingMode = RoundingMode.HALF_UP
        return format.format(value)
    }

    fun formatDuration(totalSeconds: Long, alwaysShowHour: Boolean = false): String {
        val seconds = totalSeconds % 60
        val minutes = totalSeconds / 60 % 60
        val hours = totalSeconds / 3600
        return if (hours > 0 || alwaysShowHour) {
            String.format("%02d:%02d:%02d", hours, minutes, seconds)
        } else {
            String.format("%02d:%02d", minutes, seconds)
        }
    }

    fun formatBytes(bytes: Long): String {
        if (bytes in 0..999) {
            return "$bytes B"
        }

        var newBytes = bytes
        val ci = StringCharacterIterator("kMGTPE")
        while (newBytes <= -999950 || newBytes >= 999950) {
            newBytes /= 1000
            ci.next()
        }

        return String.format("%.1f %cB", newBytes / 1000.0, ci.current())
    }

    fun formatFileSize(size: Long): String {
        if (size <= 0) {
            return "0 KB"
        }

        val kBSize = size / 1024
        if (kBSize < 1) {
            return "$size B"
        }

        val mBSize = kBSize / 1024
        if (mBSize < 1) {
            return "${
                BigDecimal(kBSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()
            } KB"
        }

        val mGSize = mBSize / 1024
        if (mGSize < 1) {
            return "${
                BigDecimal(mBSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()
            } MB"
        }

        val mTSize = mGSize / 1024
        if (mTSize < 1) {
            return "${
                BigDecimal(mGSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()
            } GB"
        }
        return "${
            BigDecimal(mTSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()
        } TB"
    }

    fun formatXml(xml: String): String {
        return try {
            val documentFactory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
            // This flag is required for security reasons
            documentFactory.isExpandEntityReferences = false

            val documentBuilder: DocumentBuilder = documentFactory.newDocumentBuilder()
            val inputSource = InputSource(ByteArrayInputStream(xml.toByteArray(Charset.defaultCharset())))
            val document: Document = documentBuilder.parse(inputSource)

            val domSource = DOMSource(document)
            val writer = StringWriter()
            val result = StreamResult(writer)

            TransformerFactory.newInstance().apply {
                setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
            }.newTransformer().apply {
                setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
                setOutputProperty(OutputKeys.INDENT, "yes")
                transform(domSource, result)
            }
            writer.toString()
        } catch (ignore: SAXParseException) {
            xml
        } catch (ignore: IOException) {
            xml
        } catch (ignore: TransformerException) {
            xml
        }
    }

    const val BYTE_SCALE: Long = 1024

/**
     * bps 比特率
     *
     * @param bytes
     * @return*/


    @JvmStatic
    fun formatTcpSpeed(bytes: Long): String {
        if (bytes <= 0) {
            return "0bps"
        }
        return if (bytes >= BYTE_SCALE) {
            String.format(Locale.getDefault(), "%dkbps", bytes / BYTE_SCALE)
        } else {
            String.format(Locale.getDefault(), "%dbps", bytes)
        }
    }


    /*fun formatJson(json: String): String {
        try {
           return JSONObject(json).toString(4)
        } catch (e: Exception) {
           Timber.d("format json error:$e")
        }
        return json
    }*/

    // ===============
    // = 数字、中文互转 =
    // ===============
    // 零索引
    private const val ZERO = 0

    // 十位数索引
    private const val TEN_POS = 10

    // 中文 ( 数字单位 )
    private val CHN_NUMBER_UNITS = arrayOf(
        "零",
        "一",
        "二",
        "三",
        "四",
        "五",
        "六",
        "七",
        "八",
        "九",
        "十",
        "百",
        "千",
        "万",
        "亿",
        "兆"
    )

    // 中文大写 ( 数字单位 )
    private val CHN_NUMBER_UPPER_UNITS = arrayOf(
        "零",
        "壹",
        "贰",
        "叁",
        "肆",
        "伍",
        "陆",
        "柒",
        "捌",
        "玖",
        "拾",
        "佰",
        "仟",
        "万",
        "亿",
        "兆"
    )

    // 数字单位对应数值
    private val NUMBER_UNITS = doubleArrayOf(
        0.0,
        1.0,
        2.0,
        3.0,
        4.0,
        5.0,
        6.0,
        7.0,
        8.0,
        9.0,
        10.0,
        100.0,
        1000.0,
        10000.0,
        100000000.0,
        1000000000000.0
    )

    // =============
    // = 对外公开方法 =
    // =============

/**
     * 数字转中文数值
     * @param number  数值
     * @param isUpper 是否大写金额
     * @return 数字中文化字符串*/


    fun numberToCHN(
        number: Double,
        isUpper: Boolean
    ): String? {
        try {
            return numberToCHNNumber(
                BigDecimal.valueOf(number),
                if (isUpper) CHN_NUMBER_UPPER_UNITS else CHN_NUMBER_UNITS
            )
        } catch (e: java.lang.Exception) {
            Timber.d( e, "numberToCHN")
        }
        return null
    }

/**
     * 数字转中文数值
     * @param number  数值
     * @param isUpper 是否大写金额
     * @return 数字中文化字符串*/


    fun numberToCHN(
        number: String,
        isUpper: Boolean
    ): String? {
        try {
           return numberToCHNNumber(
                    BigDecimal(number),
                    if (isUpper) CHN_NUMBER_UPPER_UNITS else CHN_NUMBER_UNITS
                )
        } catch (e: java.lang.Exception) {
               Timber.d( e, "numberToCHN")
        }
        return null
    }

/**
     * 数字转中文数值
     * @param number  数值
     * @param isUpper 是否大写金额
     * @return 数字中文化字符串*/


    fun numberToCHN(
        number: BigDecimal,
        isUpper: Boolean
    ): String {
        return numberToCHNNumber(number, if (isUpper) CHN_NUMBER_UPPER_UNITS else CHN_NUMBER_UNITS)
    }

    // ==========
    // = 内部方法 =
    // ==========

/**
     * 数字转中文数值
     * @param bigDecimal 数值
     * @param chnUnits   中文数字单位数组
     * @return 数字中文化字符串*/


    private fun numberToCHNNumber(
        bigDecimal: BigDecimal,
        chnUnits: Array<String>
    ): String {
        // 去除小数点
        var number: BigDecimal = bigDecimal.setScale(0, BigDecimal.ROUND_DOWN)
        // 防止小于 1, 直接返回 0
        if (number.toDouble() < 1) return chnUnits[ZERO]
        val builder = StringBuilder()
        // 索引记录
        var unitIndex = 0 // 当前数字单位
        var beforeUnitIndex = 0 // 之前的数字单位
        // 循环处理
        for (i in NUMBER_UNITS.size - 1 downTo 1) {
            if (number.toDouble() >= NUMBER_UNITS[i]) {
                val multiple = (number.toDouble() / NUMBER_UNITS[i]) as Int // 倍数
                number = number.subtract(BigDecimal.valueOf(multiple * NUMBER_UNITS[i])) // 递减
                // 如果倍数大于万倍, 则直接递归
                if (multiple >= 10000) {
                    // 拼接数值
                    builder.append(numberToCHNNumber(BigDecimal(multiple), chnUnits))
                    builder.append(chnUnits[i]) // 数字单位
                    // 判断是否需要补零
                    if (unitIndex > i) {
                        builder.append(chnUnits[ZERO]) // 补零
                    }
                } else {
                    // 判断 兆级与亿级、亿级与万级 补零操作
                    if (i == 14 && unitIndex == 15 || i == 13 && unitIndex == 14) {
                        if (multiple < 1000) {
                            builder.append(chnUnits[ZERO]) // 补零
                        }
                    } else if (unitIndex > i) { // 跨数字单位处理
                        builder.append(chnUnits[ZERO]) // 补零
                    }
                    // 拼接数值
                    builder.append(thousandConvertCHN(multiple, chnUnits))
                    builder.append(chnUnits[i]) // 数字单位
                }
                // 保存旧的数字单位索引
                beforeUnitIndex = unitIndex
                // 保存新的数字单位索引
                unitIndex = i
            }
            val numberValue: Double = number.toDouble()
            // 如果位数小于万位 ( 属于千位 ), 则进行处理
            if (numberValue < 10000) {
                // 判断是否需要补零
                if (unitIndex >= TEN_POS + 3) {
                    // 是否大于 1 ( 结尾零, 则不补充数字单位 )
                    if (numberValue >= 1) {
                        if (beforeUnitIndex == 0) {
                            beforeUnitIndex = unitIndex
                        }
                        // 如果旧的索引, 大于当前索引, 则补零
                        if (unitIndex != 13 && (beforeUnitIndex == 14 || beforeUnitIndex == 15) && beforeUnitIndex >= unitIndex) { // 属于亿、兆级别, 都需要补零
                            builder.append(chnUnits[ZERO]) // 补零
                        } else { // 当前数字单位属于万级
                            if (numberValue < 1000) {
                                builder.append(chnUnits[ZERO]) // 补零
                            }
                        }
                    }
                }
                // 拼接数值
                builder.append(thousandConvertCHN(numberValue.toInt(), chnUnits))
                return builder.toString()
            }
        }
        return builder.toString()
    }

/**
     * 万位以下 ( 千位 ) 级别转换
     * @param value    数值
     * @param chnUnits 中文数字单位数组
     * @return 数字中文化字符串*/


    private fun thousandConvertCHN(
        value: Int,
        chnUnits: Array<String>
    ): String {
        val builder = StringBuilder()
        // 转换数字
        var number = value
        // 对应数值
        val units = intArrayOf(10, 100, 1000)
        // 判断是否需要补零
        val zeros = BooleanArray(3)
        zeros[0] = number >= 10 // 大于等于十位, 才需要补零
        zeros[1] = number >= 100 // 大于等于百位, 才需要补零
        zeros[2] = false // 千位数, 不需要补零
        // 循环处理
        for (i in 2 downTo 0) {
            if (number >= units[i]) {
                val multiple = number / units[i]
                number -= multiple * units[i]
                // 拼接数值
                builder.append(chnUnits[multiple]) // 个位数
                builder.append(chnUnits[TEN_POS + i]) // 数字单位
                // 进行改变处理
                zeros[i] = false
            }
            // 补零判断处理
            if (number >= 1 && zeros[i]) {
                if (i == 1) { // 属于百位
                    builder.append(chnUnits[ZERO]) // 补零
                } else { // 属于十位
                    // 如果百位, 补零了, 个位数则不用补零
                    if (!zeros[i + 1]) {
                        builder.append(chnUnits[ZERO]) // 补零
                    }
                }
            }
        }
        // 判断最后值, 是否大于 1 ( 结尾零, 则不补充数字单位 )
        if (number >= 1) {
            builder.append(chnUnits[number])
        }
        return builder.toString()
    }

/**
     * Creates a [DecimalFormat] using the provided pattern and rounding mode.*/


    fun getDecimalFormat(
        pattern: String,
        roundingMode: RoundingMode = RoundingMode.HALF_UP,
    ): DecimalFormat = DecimalFormat(pattern).apply {
        this.roundingMode = roundingMode
    }
}
